﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using BoxSharp;

namespace BoxSharpScripting
{
    public class BoxSharpScriptWindow : Window
    {
        private AddonInterface myInterface;
        private TextBox textCode;
        private System.Windows.Media.Animation.Storyboard fadeInStory;
        private System.Windows.Media.Animation.Storyboard fadeOutStory;

        public BoxSharpScriptWindow(AddonInterface theInterface)
        {
            myInterface = theInterface;

            this.WindowStyle = WindowStyle.None;
            this.Title = "";
            this.Visibility = Visibility.Hidden;
            this.Topmost = true;
            this.SizeToContent = SizeToContent.WidthAndHeight;
            this.ShowInTaskbar = false;
            this.WindowStartupLocation = WindowStartupLocation.Manual;

            this.Left = myInterface.MainWindow.Left;
            this.Top = myInterface.MainWindow.Top + myInterface.MainWindow.ActualHeight + 10;

            DockPanel container = new DockPanel();
            this.Content = container;

            StackPanel panelControls = new StackPanel();
            panelControls.Orientation = Orientation.Horizontal;
            panelControls.SetValue(DockPanel.DockProperty, Dock.Top);

            Button buttonOpenCode = new Button();
            buttonOpenCode.Click += new RoutedEventHandler(buttonOpenCode_Click);
            buttonOpenCode.Content = "Open...";
            buttonOpenCode.Margin = new Thickness(10);
            buttonOpenCode.MinHeight = 24;
            buttonOpenCode.MinWidth = 80;

            Button buttonSaveCode = new Button();
            buttonSaveCode.Click += new RoutedEventHandler(buttonSaveCode_Click);
            buttonSaveCode.Content = "Save...";
            buttonSaveCode.Margin = new Thickness(10);
            buttonSaveCode.MinHeight = 24;
            buttonSaveCode.MinWidth = 80;

            Button buttonRun = new Button();
            buttonRun.Click += new RoutedEventHandler(buttonRun_Click);
            buttonRun.Content = "Run";
            buttonRun.Margin = new Thickness(10);
            buttonRun.MinHeight = 24;
            buttonRun.MinWidth = 80;

            Button buttonClose = new Button();
            buttonClose.Click += new RoutedEventHandler(buttonClose_Click);
            buttonClose.Content = "Close";
            buttonClose.Margin = new Thickness(10);
            buttonClose.MinHeight = 24;
            buttonClose.MinWidth = 80;

            panelControls.Children.Add(buttonOpenCode);
            panelControls.Children.Add(buttonSaveCode);
            panelControls.Children.Add(buttonRun);
            panelControls.Children.Add(buttonClose);

            container.Children.Add(panelControls);

            textCode = new TextBox();
            textCode.SetValue(DockPanel.DockProperty, Dock.Bottom);
            textCode.Margin = new Thickness(10);
            textCode.AcceptsReturn = true;
            textCode.AcceptsTab = true;

            container.Children.Add(textCode);

            fadeInStory = new System.Windows.Media.Animation.Storyboard();
            System.Windows.Media.Animation.DoubleAnimation fadeInAni = new System.Windows.Media.Animation.DoubleAnimation(0, 1, new Duration(TimeSpan.FromSeconds(0.4)));
            System.Windows.Media.Animation.Storyboard.SetTargetProperty(fadeInAni, new PropertyPath(UIElement.OpacityProperty));
            fadeInStory.Children.Add(fadeInAni);

            fadeOutStory = new System.Windows.Media.Animation.Storyboard();
            System.Windows.Media.Animation.DoubleAnimation fadeOutAni = new System.Windows.Media.Animation.DoubleAnimation(1, 0, new Duration(TimeSpan.FromSeconds(0.4)));
            System.Windows.Media.Animation.Storyboard.SetTargetProperty(fadeOutAni, new PropertyPath(UIElement.OpacityProperty));
            fadeOutStory.Children.Add(fadeOutAni);

            fadeOutStory.Completed += new EventHandler(fadeOutStory_Completed);
        }

        void buttonSaveCode_Click(object sender, RoutedEventArgs e)
        {
            Microsoft.Win32.SaveFileDialog diag = new Microsoft.Win32.SaveFileDialog();
            diag.AddExtension = true;
            diag.CreatePrompt = false;
            diag.DefaultExt = "txt";
            diag.Filter = "Text Code Files (*.txt)|*.txt";
            diag.InitialDirectory = myInterface.Path;
            diag.OverwritePrompt = true;

            diag.ShowDialog();
        }

        void buttonOpenCode_Click(object sender, RoutedEventArgs e)
        {
            Microsoft.Win32.OpenFileDialog diag = new Microsoft.Win32.OpenFileDialog();
            diag.CheckFileExists = true;
            diag.AddExtension = true;
            diag.DefaultExt = "txt";
            diag.InitialDirectory = myInterface.Path;
            diag.Multiselect = false;
            diag.Filter = "Text Code Files (*.txt)|*.txt";

            bool? dlgresult = diag.ShowDialog();

            if (dlgresult.HasValue)
            {
                if (dlgresult.Value)
                {
                    StreamReader reader = new StreamReader(diag.FileName);
                    textCode.Text = reader.ReadToEnd();
                    reader.Close();
                }
            }
        }

        void fadeOutStory_Completed(object sender, EventArgs e)
        {
            this.Visibility = Visibility.Hidden;
        }

        void buttonRun_Click(object sender, RoutedEventArgs e)
        {
            BoxSharpScripting.RunScript(textCode.Text, myInterface);
        }

        void buttonClose_Click(object sender, RoutedEventArgs e)
        {
            HideUp();
        }

        public void ShowUp()
        {
            this.Visibility = Visibility.Visible;
            fadeInStory.Begin(this);
            textCode.Focus();
        }

        public void HideUp()
        {
            fadeOutStory.Begin(this);
        }
    }

    public class BoxSharpScripting : AddonInstance
    {
        private AddonInterface myInterface;
        private BoxSharpScriptWindow scriptWindow;
        private ToolTip tip;

        public override void AddonMain(AddonInterface addonInterface)
        {
            myInterface = addonInterface;

            myInterface.AddonInitializing += new AddonEventHandler(myInterface_AddonInitializing);
            myInterface.AddonFinalizing += new AddonEventHandler(myInterface_AddonFinalizing);
        }

        void myInterface_AddonFinalizing()
        {
            scriptWindow.Close();
        }

        void myInterface_AddonInitializing()
        {
            scriptWindow = new BoxSharpScriptWindow(myInterface);

            tip = new ToolTip();
            tip.HasDropShadow = true;
            tip.PlacementTarget = myInterface.Box;
            tip.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom;
            tip.IsOpen = false;
            tip.Padding = new Thickness(5);
            tip.VerticalOffset = 5;

            myInterface.Box.TextChanged += new TextChangedEventHandler(Box_TextChanged);
            myInterface.OnCommand += new CommandEventHandler(myInterface_OnCommand);
        }

        void myInterface_OnCommand(string bscommand)
        {
            if (bscommand.ToLower().StartsWith("/script "))
            {
                string script = bscommand.Substring(8);

                myInterface.Box.Text = "";

                if (!RunScript(script, myInterface))
                {
                    myInterface.Box.Text = "/script " + script;
                    myInterface.Box.Focus();
                }
            }
            else if (bscommand.ToLower() == "/script")
            {
                myInterface.Box.Text = "";

                scriptWindow.ShowUp();
            }
            else if (bscommand.ToLower().StartsWith("/scriptfile "))
            {
                string orgCommand = bscommand;
                string scriptFile = bscommand.Substring(12);

                if (!scriptFile.EndsWith(".txt"))
                {
                    scriptFile = scriptFile + ".txt";
                }

                string script = "";

                if (!Path.IsPathRooted(scriptFile))
                {
                    scriptFile = Path.Combine(myInterface.Path, scriptFile);
                }

                using (StreamReader codeReader = new StreamReader(scriptFile))
                {
                    script = codeReader.ReadToEnd();
                    codeReader.Close();
                }

                myInterface.Box.Text = "";

                if (!RunScript(script, myInterface))
                {
                    myInterface.Box.Text = orgCommand;
                    myInterface.Box.Focus();
                }
            }

        }

        void Box_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (myInterface.Box.Text.ToLower() == "/script")
            {
                tip.Content = "/script\r\nOpen a window for scripting.\r\n\r\n/script code\r\nRun the code immediately.\r\n\r\n/scriptfile filename\r\nRun the script file immediately.Use relative path if the code file is under Interface/Addons/BoxSharpScripting/.";
                tip.IsOpen = true;
            }
            else if (myInterface.Box.Text.ToLower().StartsWith("/script "))
            {
                tip.Content = "/script code\r\nRun the code immediately.";
                tip.IsOpen = true;
            }
            else if (myInterface.Box.Text.ToLower().StartsWith("/scriptfile"))
            {
                tip.Content = "/scriptfile filename\r\nRun the script file immediately.Use relative path if the code file is under Interface/Addons/BoxSharpScripting/.";
                tip.IsOpen = true;
            }
            else
            {
                tip.IsOpen = false;
            }
        }

        public static bool RunScript(string script, AddonInterface myInterface)
        {
            StringBuilder codeBuilder = new StringBuilder();

            codeBuilder.AppendLine("using System;");
            codeBuilder.AppendLine("using System.IO;");
            codeBuilder.AppendLine("using System.Collections.Generic;");
            codeBuilder.AppendLine("using System.Text;");
            codeBuilder.AppendLine("using System.Windows;");
            codeBuilder.AppendLine("using System.Windows.Controls;");
            codeBuilder.AppendLine("");
            codeBuilder.AppendLine("namespace BoxSharpTempScripting");
            codeBuilder.AppendLine("{");
            codeBuilder.AppendLine("public class TempScript");
            codeBuilder.AppendLine("{");
            codeBuilder.AppendLine("private TextBox box;");
            codeBuilder.AppendLine("public void Run(Window MainWindow, StackPanel MainPanel, StackPanel Panel1, StackPanel Panel2, TextBox Box)");
            codeBuilder.AppendLine("{");
            codeBuilder.AppendLine("box=Box;");
            codeBuilder.AppendLine(script);
            codeBuilder.AppendLine("}");
            codeBuilder.AppendLine("public void Print(object val)");
            codeBuilder.AppendLine("{");
            codeBuilder.AppendLine("box.Text=val.ToString();");
            codeBuilder.AppendLine("}");
            codeBuilder.AppendLine("}");
            codeBuilder.AppendLine("}");

            string code = codeBuilder.ToString();

            string programDir = System.Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);

            CSharpCodeProvider codeProvider = new CSharpCodeProvider();

            CompilerParameters codeParams = new System.CodeDom.Compiler.CompilerParameters();
            codeParams.GenerateExecutable = false;
            codeParams.GenerateInMemory = true;
            codeParams.IncludeDebugInformation = true;
            codeParams.TreatWarningsAsErrors = true;
            codeParams.ReferencedAssemblies.Add(@"System.dll");
            codeParams.ReferencedAssemblies.Add(@"System.Data.dll");
            codeParams.ReferencedAssemblies.Add(@"System.Xml.dll");
            codeParams.ReferencedAssemblies.Add(Path.Combine(programDir, @"Reference Assemblies\Microsoft\Framework\v3.0\PresentationCore.dll"));
            codeParams.ReferencedAssemblies.Add(Path.Combine(programDir, @"Reference Assemblies\Microsoft\Framework\v3.0\PresentationFramework.dll"));
            codeParams.ReferencedAssemblies.Add(Path.Combine(programDir, @"Reference Assemblies\Microsoft\Framework\v3.5\System.Core.dll"));
            codeParams.ReferencedAssemblies.Add(Path.Combine(programDir, @"Reference Assemblies\Microsoft\Framework\v3.5\System.Data.DataSetExtensions.dll"));
            codeParams.ReferencedAssemblies.Add(Path.Combine(programDir, @"Reference Assemblies\Microsoft\Framework\v3.5\System.Xml.Linq.dll"));
            codeParams.ReferencedAssemblies.Add(Path.Combine(programDir, @"Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationProvider.dll"));
            codeParams.ReferencedAssemblies.Add(Path.Combine(programDir, @"Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll"));

            CompilerResults compilerResults = codeProvider.CompileAssemblyFromSource(codeParams, code);

            if (compilerResults.Errors.Count > 0)
            {
                string errorText = "";

                foreach (CompilerError error in compilerResults.Errors)
                {
                    errorText += "Line:" + (error.Line - 15).ToString() + "\tColumn:" + error.Column.ToString() + "\r\n" + error.ErrorText + "\r\n\r\n";
                }

                MessageBox.Show("Script failed to run. Here's the error message:\r\n\r\n" + errorText, "Error", MessageBoxButton.OK, MessageBoxImage.Error);

                return false;
            }
            else
            {
                object program = compilerResults.CompiledAssembly.CreateInstance("BoxSharpTempScripting.TempScript");
                compilerResults.CompiledAssembly.GetExportedTypes()[0].GetMethod("Run").Invoke(program, new object[] { myInterface.MainWindow, myInterface.MainPanel, myInterface.Panel1, myInterface.Panel2, myInterface.Box });
            }

            return true;
        }
    }
}
